package net.stratusprogramming.school.Game.World;

import net.stratusprogramming.school.Game.Framework.System.Config;
import net.stratusprogramming.school.Game.Framework.System.InstanceMgr;
import net.stratusprogramming.school.Game.Framework.System.Logger;
import net.stratusprogramming.school.Game.Framework.System.SaveMgr;
import net.stratusprogramming.school.Game.Screens.FatalError;
import org.newdawn.slick.SlickException;
import org.newdawn.slick.geom.Rectangle;
import org.newdawn.slick.tiled.TiledMap;

public class Map {
	
	private String mapid;
	private TiledMap map;
	
	/**
	 * Loads a new map
	 * 
	 * TMX file scheme: "res/maps/{identifier}.tmx"
	 * Tiles should be in folder: "res/images/tilesets"
	 * 
	 * @param identifier string of map name
	 */
	public void loadMap(String identifier) {
		mapid = identifier;
		try {
			map = new TiledMap("res/maps/"+identifier+".tmx", "res/images/");
		} catch (SlickException e) {
			e.printStackTrace();
		}
	}
	
	/**
	 * Renders the tiled map faster by only rendering necessary tiles.
	 * 
	 * With a 25x10 map I only got 0.08ms faster which is nothing, but it
	 * is faster, and I wrote it so why let it got to waste. As the map gets
	 * large is should save a lot more time. 
	 * 
	 * @param offsetX of map
	 * @param offsetY of map
	 * @param type only layers with property type set to this will be rendered
	 */
	public void renderMap(int offsetX, int offsetY, String type) {
		// new origin of map
		int mapX = (offsetX >= 0) ? offsetX : offsetX % map.getTileWidth();
		int mapY = (offsetY >= 0) ? offsetY : offsetY % map.getTileHeight();
		
		// find block needed to start rendering from
		int tileIndexX = (offsetX >= 0) ? 0 : -((offsetX - mapX) / map.getTileWidth());
		int tileIndexY = (offsetY >= 0) ? 0 : -((offsetY - mapY) / map.getTileHeight());
		
		// find width and height (in blocks) that needs to be rendered
		int xDistToEdge = Config.WINDOW_WIDTH - mapX;
		int width = (int)Math.ceil(xDistToEdge / (float)map.getTileWidth()); // cast float to prevent int truncation
		int yDistToEdge = Config.WINDOW_HEIGHT - mapY;
		int height = (int)Math.ceil(yDistToEdge / (float)map.getTileHeight()); // cast float to prevent int truncation
		
		for(int i = 0; i < map.getLayerCount(); i++) {
			if(map.getLayerProperty(i, "type", "").equalsIgnoreCase(type))
				map.render(mapX, mapY, tileIndexX, tileIndexY, width, height, i, false);
		}
	}
	
	public boolean isBlocked(Rectangle player) {
		// don't run off map
		float x = player.getX();
		float y = player.getY();
		if(x < 0 || y < 0 || x+player.getWidth() > map.getWidth()*map.getTileWidth() || 
		   y+player.getHeight() > map.getHeight()*map.getTileHeight()) return true;
		
		// check for collisions with objects
		for(int g = 0; g < map.getObjectGroupCount(); g++) {
			for(int o = 0; o < map.getObjectCount(g); o++) {
				if(map.getObjectType(g, o).equalsIgnoreCase("blocked")) {
					Rectangle tile = new Rectangle(map.getObjectX(g, o),
												   map.getObjectY(g, o),
												   map.getObjectWidth(g, o),
												   map.getObjectHeight(g, o));
					
					if(player.intersects(tile) || player.contains(tile)) return true;
	        	}
			}
        }
        
        return false;
    }
	
	public void save(SaveMgr saveMgr) {
		saveMgr.setSetting("map-id", mapid);
		
	}

	public void load(SaveMgr saveMgr) {
		if(saveMgr.hasSetting("map-id")) {
			mapid = saveMgr.getSetting("map-id");
			loadMap(mapid);
		} else {
			Logger.logln("MapMgr", "FATAL ERROR: No map-id. Save corrupted.");
			InstanceMgr.main.setScreen(new FatalError());
		}
	}	
	
	// gets/sets
	public int getTileHeight() {
		return map.getTileHeight();
	}
	
	public int getTileWidth() {
		return map.getTileWidth();
	}
	
	public TiledMap getMap() {
		return map;
	}
}
